查看原文
其他

文件上传下载,这样做最简单~

鱼皮 程序员鱼皮 2024-01-30

大家好,我是程序员鱼皮,前段时间带大家做的 代码生成平台项目 中,需要用到文件上传下载功能。这是我们在开发项目中常用的 通用能力,所以我单独从项目教程中抽了一节小教程,给大家分享一种文件上传下载的实现方式,非常简单!

照顾移动端同学的阅读感受,以下为部分教程,完整教程和代码可以在我的 编程宝典网站(codefather.cn)或 编程导航 中查看。

基本思路

首先我们要思考:将文件上传到哪里?从哪里下载?

最简单的方式就是上传到后端项目所在的服务器,直接使用 Java 自带的文件读写 API 就能实现。但是,这种方式存在不少缺点,比如:

  1. 不利于扩展:单个服务器的存储是有限的,如果存满了,只能再新增存储空间或者清理文件。
  2. 不利于迁移:如果后端项目要更换服务器部署,之前所有的文件都要迁移到新服务器,非常麻烦。
  3. 不够安全:如果忘记控制权限,用户很有可能通过恶意代码访问服务器上的文件,而且想控制权限也比较麻烦,需要自己实现。
  4. 不利于管理:只能通过一些文件管理器进行简单的管理操作,但是缺乏数据处理、流量控制等多种高级能力。

因此,除了存储一些需要清理的临时文件之外,我们通常不会将用户上传并保存的文件(比如用户头像)直接上传到服务器,而是更推荐大家使用专业的第三方存储服务,专业的工具做专业的事。其中,最常用的便是 对象存储

什么是对象存储?

对象存储是一种存储 海量文件分布式 存储服务,具有高扩展性、低成本、可靠安全等优点。

比如开源的对象存储服务 MinIO,还有商业版的云服务,像亚马逊 S3(Amazon S3)、阿里云对象存储(OSS)、腾讯云对象存储(COS)等等。

我个人更推荐大家使用第三方云服务,不要自己再去搭建 MinIO 之类的,咱学习主打一个快速!

本教程中,将用腾讯云的 COS 带大家实现文件的上传和下载。

创建并使用

首先进入对象存储控制台,创建存储桶。

可以把存储桶理解为一个存储空间,和文件系统类似,都是根据路径找到文件或目录。可以多个项目共用一个存储桶,也可以每个项目一个。

点击创建存储桶,注意地域选择国内(离用户较近的位置)。此处访问权限先选择“公有读私有写”,因为我们的存储桶要存储允许用户公开访问的代码生成器图片。而如果整个存储桶要存储的文件都不允许用户访问,建议选择私有读写,更安全。

默认告警一定要勾选! 因为对象存储服务的存储和访问流量都是计费的,超限后我们要第一时间得到通知并进行相应的处理。

不过也不用太担心,自己做项目的话一般是没人攻击你的,而且对象存储很便宜,正常情况下消耗的费用寥寥无几。

然后一直点击“下一步”即可。

开通成功后,我们可以试着使用 web 控制台上传和浏览文件。当然,一般情况下我们会使用程序来操作存储桶,下面就来实现。

后端操作对象存储

如何在 Java 程序中使用对象存储呢?

其实非常简单,一般情况下,第三方服务都会提供比较贴心的文档教程,比如这里我们参考官方的快速入门或 Java SDK 文档,就能快速入门基本操作(增删改查都有)。

文档地址:https://cloud.tencent.com/document/product/436/10199

1、初始化客户端

参考官方文档,我们要先初始化一个 COS 客户端对象,和对象存储服务进行交互。

对于规模不大的项目,只需要复用一个 COS 客户端对象即可,所以我们可以通过编写配置类初始化客户端对象。

1)打开 Spring Boot 项目,在 config 目录下新建 CosClientConfig 类。负责读取配置文件,并创建一个 COS 客户端的 Bean。

示例代码如下:

@Configuration
@ConfigurationProperties(prefix = "cos.client")
@Data
public class CosClientConfig {

  /**
   * accessKey
   */

  private String accessKey;

  /**
   * secretKey
   */

  private String secretKey;

  /**
   * 区域
   */

  private String region;

  /**
   * 桶名
   */

  private String bucket;

  @Bean
  public COSClient cosClient() {
    // 初始化用户身份信息(secretId, secretKey)
    COSCredentials cred = new BasicCOSCredentials(accessKey, secretKey);
    // 设置bucket的区域
    ClientConfig clientConfig = new ClientConfig(new Region(region));
    // 生成cos客户端
    return new COSClient(cred, clientConfig);
  }
}

2)填写配置文件。

一定要注意防止密码泄露! 所以我们新建 application-local.yml 文件,并且在 .gitignore 中忽略该文件的提交,这样就不会将代码等敏感配置提交到代码仓库了。

配置代码如下:

# 本地配置文件
# 对象存储
cos:
  client:
    accessKey: xxx
    secretKey: xxx
    region: xxx
    bucket: xxx

可以参考官方文档分别获取需要的配置。

2、通用能力类

新建 CosManager 类,提供通用的对象存储操作,比如文件上传、文件下载等,供其他代码(比如 Service)调用。

该类需要引入对象存储配置和 COS 客户端,用于和 COS 进行交互。参考代码如下:

@Component
public class CosManager {

    @Resource
    private CosClientConfig cosClientConfig;

    @Resource
    private COSClient cosClient;

    ... 一些操作 COS 的方法
}

3、文件上传

参考官方文档的“上传对象”部分,可以编写出文件上传的代码。

1)CosManager 新增上传对象的方法,代码如下:

/**
 * 上传对象
 *
 * @param key  唯一键
 * @param file 文件
 * @return
 */

public PutObjectResult putObject(String key, File file) {
    PutObjectRequest putObjectRequest = new PutObjectRequest(
        cosClientConfig.getBucket(), key, file);
    return cosClient.putObject(putObjectRequest);
}

2)用一个常量类 FileConstant 记录 COS 访问域名,便于接下来测试访问已上传的文件。

示例代码如下:

public interface FileConstant {

    /**
     * COS 访问地址
     */

    String COS_HOST = "自己的 COS 访问地址,云服务控制台可查";
}

3)为了方便测试,在 FileController 中编写测试文件上传接口。

核心流程是先接受用户上传的文件,指定上传的路径,然后调用 cosManager.putObject 方法上传文件到 COS 对象存储;上传成功后,会返回一个文件的 key(其实就是文件路径),便于我们访问和下载文件。

测试文件上传接口代码如下:

@PostMapping("/test/upload")
public BaseResponse<String> testUploadFile(@RequestPart("file") MultipartFile multipartFile) {
    // 文件目录
    String filename = multipartFile.getOriginalFilename();
    String filepath = String.format("/test/%s", filename);
    File file = null;
    try {
        // 上传文件
        file = File.createTempFile(filepath, null);
        multipartFile.transferTo(file);
        cosManager.putObject(filepath, file);
        // 返回可访问地址
        return ResultUtils.success(filepath);
    } catch (Exception e) {
        log.error("file upload error, filepath = " + filepath, e);
        throw new BusinessException(ErrorCode.SYSTEM_ERROR, "上传失败");
    } finally {
        if (file != null) {
            // 删除临时文件
            boolean delete = file.delete();
            if (!delete) {
                log.error("file delete error, filepath = {}", filepath);
            }
        }
    }
}

需要注意,如果是线上项目,测试接口一定要加上管理员权限!防止任何用户随意上传文件。

4)测试接口

使用 local 配置启动项目:

打开 Swagger 接口文档,测试文件上传:

4、文件下载

官方文档介绍了 2 种文件下载方式。一种是直接下载 COS 的文件到后端服务器(适合服务器端处理文件),另一种是获取到文件下载输入流(适合返回给前端用户)。

其实还有第三种“下载方式”,直接通过路径链接访问,适用于单一的、可以被用户公开访问的资源,比如用户头像、本项目中的代码生成器图片。

1)首先在 CosManager 中新增对象下载方法,根据对象的 key 获取存储信息:

public COSObject getObject(String key) {
    GetObjectRequest getObjectRequest = new GetObjectRequest(cosClientConfig.getBucket(), key);
    return cosClient.getObject(getObjectRequest);
}

2)为了方便测试,在 FileController 中编写测试文件下载接口。

核心流程是根据路径获取到 COS 文件对象,然后将文件对象转换为文件流,并写入到 Servlet 的 Response 对象中。注意要设置文件下载专属的响应头。

测试文件下载接口代码如下:

@GetMapping("/test/download/")
public void testDownloadFile(String filepath, HttpServletResponse response) throws IOException {
    COSObjectInputStream cosObjectInput = null;
    try {
        COSObject cosObject = cosManager.getObject(filepath);
        cosObjectInput = cosObject.getObjectContent();
        // 处理下载到的流
        byte[] bytes = IOUtils.toByteArray(cosObjectInput);
        // 设置响应头
        response.setContentType("application/octet-stream;charset=UTF-8");
        response.setHeader("Content-Disposition""attachment; filename=" + filepath);
        // 写入响应
        response.getOutputStream().write(bytes);
        response.getOutputStream().flush();
    } catch (Exception e) {
        log.error("file download error, filepath = " + filepath, e);
        throw new BusinessException(ErrorCode.SYSTEM_ERROR, "下载失败");
    } finally {
        if (cosObjectInput != null) {
            cosObjectInput.close();
        }
    }
}

3)启动项目,打开 Swagger 接口文档,测试文件下载:

至此,后端操作对象存储的代码已编写完成,下面写一个前端页面来测试文件的上传和下载。

前端文件上传 / 下载

1)首先使用 openAPI 工具生成接口(或者自己编写类似的代码)。

不知道怎么生成前端代码的同学,可以去补下 鱼皮的项目教程

可以看到工具为我们生成了文件上传请求函数,等会儿直接用就行,爽歪歪~

2)新建文件上传下载测试页面,并添加路由。

3)和后端一样,新增对象存储相关常量。

/**
 * COS 访问地址
 */

export const COS_HOST = "https://yuzi-1256524210.cos.ap-shanghai.myqcloud.com";

4)开发页面。

对于文件上传,直接使用 Ant Design 的 Upload 拖拽文件上传组件。

对于文件下载,使用 img 标签直接拼接图片地址并展示、再写一个按钮来触发文件下载。

页面效果如图:

文件上传参考代码如下,我们在 customRequest 字段中自定义了上传文件的请求逻辑:

const props: UploadProps = {
name: 'file',
multiple: false,
maxCount: 1,
customRequest: async (fileObj: any) => {
try {
const res = await testUploadFileUsingPost({}, fileObj.file);
fileObj.onSuccess(res.data);
setValue(res.data);
} catch (e: any) {
message.error('上传失败,' + e.message);
fileObj.onError(e);
}
},
onRemove() {
setValue(undefined);
},
};

<Card title="文件上传">
<Dragger {...props}>
<p className="ant-upload-drag-icon">
<InboxOutlined />
</p>
<p className="ant-upload-text">Click or drag file to this area to upload</p>
<p className="ant-upload-hint">
Support for a single or bulk upload. Strictly prohibited from uploading company data or
other banned files.
</p>
</Dragger>
</Card>

对于文件下载,可以使用 file-saver 库,将后端返回的 blob 内容转化为文件。

先安装 file-saver 库:

npm install file-saver
npm i --save-dev @types/file-saver

下载文件代码如下:

import { saveAs } from 'file-saver';

<Button
onClick={async () => {
const blob = await testDownloadFileUsingGet({
filepath: value,
}, {
responseType: "blob",
});
// 使用 file-saver 来保存文件
const fullPath = COS_HOST + value;
saveAs(blob, fullPath.substring(fullPath.lastIndexOf("/") + 1));
}}
>
点击下载文件
</Button>

至此,通用的文件上传和下载功能已开发完成。核心代码都给了,大家感兴趣自己实践下。

实践

鱼皮原创的定制化代码生成项目中,演示了如何使用对象存储实现代码生成器文件的上传和下载。

感兴趣的同学欢迎加入 编程导航 ,跟着鱼皮一起学编程、做项目~

👇🏻 点击下方阅读原文,获取鱼皮往期编程干货。

往期推荐

考研结束,抓紧做这件事!

学编程,千万不要轻易转行!

又又又崩了?盘点 23 年十大线上事故!

我们的 AI 小工具,丝滑~

小程序开发,这个框架轻松拿捏~

命令模式,这么学就很简单!

继续滑动看下一个

文件上传下载,这样做最简单~

鱼皮 程序员鱼皮
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存